package com.kapuscinski.saferoute;

import java.util.HashSet;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnCameraChangeListener;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.GroundOverlayOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.kapuscinski.saferoute.database.DataModel;
import com.kapuscinski.saferoute.database.DatabaseContract.RoughnessEntry;
import com.kapuscinski.saferoute.database.DatabaseHelper;
import com.kapuscinski.saferoute.logic.RoughnessLocationMonitor;
import com.kapuscinski.saferoute.logic.RoughnessLocationMonitor.RoughnessLocationListener;

public class MainActivity extends Activity implements RoughnessLocationListener {

	private static final String TAG = "MainActivity";
	private static final float BITMAP_TRANSPARENCY = 0f;
	private static final float BITMAP_SIZE = 40f;
	private static final float GOOD_ACCURACY = 4f;
	private static final int BITMAP_PIXEL_SIZE = 80;

	private RoughnessLocationMonitor roughnessMonitor;
	private DatabaseHelper dbHelper;
	private Marker marker;
	private Bitmap smallBitmap, mediumBitmap, bigBitmap;

	private GoogleMap map;
	private TextView accuracy, fps, sats, satsInUse, speed;
	private RelativeLayout gpsData;

	private HashSet<DataModel> pointsSet = new HashSet<DataModel>();
	private boolean showGPSData = true, workOnPause = true;
	private int counter;
	private float startNano, endNano, zoom = 16, thresholdSmall,
			thresholdMedium, thresholdBig;

	// variables for animating the marker
	private double newLat, newLng;
	private int animCounter;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		createRoughnessBitmaps();

		// setup accelerometer and gps sensor
		roughnessMonitor = new RoughnessLocationMonitor(this);

		// setup db
		dbHelper = new DatabaseHelper(this);
		dbHelper.openDatabaseInWriteMode();

		// setup UI
		gpsData = (RelativeLayout) findViewById(R.id.main_gpsData);
		fps = (TextView) findViewById(R.id.main_fps);
		accuracy = (TextView) findViewById(R.id.main_accuracy);
		sats = (TextView) findViewById(R.id.main_sats);
		satsInUse = (TextView) findViewById(R.id.main_satsInUse);
		speed = (TextView) findViewById(R.id.main_speed);
		map = ((MapFragment) getFragmentManager().findFragmentById(
				R.id.main_map)).getMap();

		// make initial setup to the map
		marker = map.addMarker(new MarkerOptions().position(new LatLng(0, 0)));
		map.getUiSettings().setZoomControlsEnabled(true);
		map.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(50.082006,
				19.915390), zoom));
		map.setOnCameraChangeListener(new OnCameraChangeListener() {

			@Override
			public void onCameraChange(CameraPosition camera) {
				// update zoom variable
				zoom = camera.zoom;

				// load from db needed points
				addPointsToMap(dbHelper.getCursorForBounds(map.getProjection()
						.getVisibleRegion().latLngBounds));
			}
		});

	}

	public void addPointsToMap(Cursor cursor) {
		if (cursor.getCount()==0) {
			Log.d(TAG, "No points to add");
			return;
		}

		Log.d(TAG, "Fetched: " + cursor.getCount() + " points from databse");

		int col_acceleration = cursor
				.getColumnIndex(RoughnessEntry.ACCELERATION);
		int col_accuracy = cursor.getColumnIndex(RoughnessEntry.ACCURACY);
		int col_latitude = cursor.getColumnIndex(RoughnessEntry.LATITUDE);
		int col_longitude = cursor.getColumnIndex(RoughnessEntry.LONGITUDE);

		while (cursor.moveToNext()) {
			Bitmap bitmap = determineBitmapColor(cursor
					.getFloat(col_acceleration));
			if (bitmap == null)
				continue;
			double latitude = cursor.getDouble(col_latitude);
			double longitude = cursor.getDouble(col_longitude);
			float acc = cursor.getFloat(col_acceleration);
			float accuracy = cursor.getFloat(col_accuracy);
			DataModel model = new DataModel();
			model.setAcceleration(acc);
			model.setLatitude(latitude);
			model.setLongitude(longitude);
			if (!pointsSet.contains(model)) {
				pointsSet.add(model);
				map.addGroundOverlay(new GroundOverlayOptions().image(
						BitmapDescriptorFactory.fromBitmap(bitmap)).position(
						new LatLng(latitude, longitude),
						determineBitmapSize(accuracy)));
			}
		}
	}

	private void animateMarker(final Marker mapMarker, final Location target) {
		LatLng prev = marker.getPosition();
		final double latDiff = target.getLatitude() - prev.latitude;
		final double lngDiff = target.getLongitude() - prev.longitude;

		newLat = prev.latitude;
		newLng = prev.longitude;
		animCounter = 0;
		final Handler handler = new Handler();
		handler.post(new Runnable() {

			@Override
			public void run() {
				newLat = latDiff / 10 + newLat;
				newLng = lngDiff / 10 + newLng;
				if (animCounter < 10) {
					mapMarker.setPosition(new LatLng(newLat, newLng));
					handler.postDelayed(this, 40);
				}
				animCounter++;
			}
		});
	}

	private void readPreferences() {
		SharedPreferences preferences = getSharedPreferences(
				SettingsActivity.KEY_FILENAME, Activity.MODE_PRIVATE);
		preferences
				.registerOnSharedPreferenceChangeListener(preferenceListener);
		thresholdSmall = preferences.getFloat(
				SettingsActivity.KEY_THRESHOLD_SMALL,
				SettingsActivity.DEFAULT_THRESHOLD_SMALL);
		thresholdMedium = preferences.getFloat(
				SettingsActivity.KEY_THRESHOLD_MEDIUM,
				SettingsActivity.DEFAULT_THRESHOLD_MEDIUM);
		thresholdBig = preferences.getFloat(SettingsActivity.KEY_THRESHOLD_BIG,
				SettingsActivity.DEFAULT_THRESHOLD_BIG);
		workOnPause = preferences.getBoolean(
				SettingsActivity.KEY_WORK_ON_PAUSE, true);
	}

	private OnSharedPreferenceChangeListener preferenceListener = new OnSharedPreferenceChangeListener() {

		@Override
		public void onSharedPreferenceChanged(SharedPreferences preferences,
				String key) {
			if (key.equals(SettingsActivity.KEY_THRESHOLD_SMALL)) {
				thresholdSmall = preferences.getFloat(
						SettingsActivity.KEY_THRESHOLD_SMALL,
						SettingsActivity.DEFAULT_THRESHOLD_SMALL);
			} else if (key.equals(SettingsActivity.KEY_THRESHOLD_MEDIUM)) {
				thresholdMedium = preferences.getFloat(
						SettingsActivity.KEY_THRESHOLD_MEDIUM,
						SettingsActivity.DEFAULT_THRESHOLD_MEDIUM);
			} else if (key.equals(SettingsActivity.KEY_THRESHOLD_BIG)) {
				thresholdBig = preferences.getFloat(
						SettingsActivity.KEY_THRESHOLD_BIG,
						SettingsActivity.DEFAULT_THRESHOLD_BIG);
			}

		}
	};

	private void createRoughnessBitmaps() {
		smallBitmap = Bitmap.createBitmap(BITMAP_PIXEL_SIZE, BITMAP_PIXEL_SIZE,
				Bitmap.Config.ARGB_8888);
		mediumBitmap = Bitmap.createBitmap(BITMAP_PIXEL_SIZE,
				BITMAP_PIXEL_SIZE, Bitmap.Config.ARGB_8888);
		bigBitmap = Bitmap.createBitmap(BITMAP_PIXEL_SIZE, BITMAP_PIXEL_SIZE,
				Bitmap.Config.ARGB_8888);

		Canvas canvas = new Canvas(smallBitmap);
		Drawable shape = getResources().getDrawable(
				R.drawable.roughness_small_drawable);
		shape.setBounds(0, 0, smallBitmap.getWidth(), smallBitmap.getHeight());
		shape.draw(canvas);

		canvas = new Canvas(mediumBitmap);
		shape = getResources()
				.getDrawable(R.drawable.roughness_medium_drawable);
		shape.setBounds(0, 0, mediumBitmap.getWidth(), mediumBitmap.getHeight());
		shape.draw(canvas);

		canvas = new Canvas(bigBitmap);
		shape = getResources().getDrawable(R.drawable.roughness_big_drawable);
		shape.setBounds(0, 0, bigBitmap.getWidth(), bigBitmap.getHeight());
		shape.draw(canvas);
	}

	@Override
	protected void onResume() {
		super.onResume();
		readPreferences();
		if (!roughnessMonitor.isGpsEnabled())
			showNoGPSDialog();
		roughnessMonitor.startMonitor(this);
	}

	@Override
	protected void onPause() {
		super.onPause();
		if (!workOnPause) {
			roughnessMonitor.stopMonitor();
		}
	}

	private void showNoGPSDialog() {
		AlertDialog.Builder dialog = new AlertDialog.Builder(this);
		dialog.setTitle(getResources().getString(R.string.gps_dialog_title));
		dialog.setMessage(getResources().getString(R.string.gps_dialog_message));
		dialog.setPositiveButton(getResources().getString(R.string.yes),
				new OnClickListener() {

					@Override
					public void onClick(DialogInterface arg0, int arg1) {
						startActivity(new Intent(
								android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
					}
				});
		dialog.setNegativeButton(getResources().getString(R.string.no), null);
		dialog.show();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		case R.id.action_settings:
			startActivity(new Intent(this, SettingsActivity.class));
			return true;
		case R.id.action_show_more:
			if (showGPSData) {
				item.setTitle(getResources().getString(
						R.string.action_data_show));
				showGPSData = false;
				gpsData.setVisibility(View.GONE);
			} else {
				item.setTitle(getResources().getString(
						R.string.action_data_hide));
				showGPSData = true;
				gpsData.setVisibility(View.VISIBLE);
			}
			return true;
		case R.id.action_cleardb:
			AlertDialog.Builder dialog = new AlertDialog.Builder(this);
			dialog.setTitle(getResources().getString(
					R.string.clear_dialog_title));
			dialog.setMessage(getResources().getString(
					R.string.clear_dialog_message));
			dialog.setPositiveButton(R.string.yes, new OnClickListener() {

				@Override
				public void onClick(DialogInterface dialog, int which) {
					Toast.makeText(
							MainActivity.this,
							String.format(
									getResources().getString(
											R.string.delete_count),
									dbHelper.deleteAllData()),
							Toast.LENGTH_LONG).show();
					dialog.dismiss();
				}
			});
			dialog.setNegativeButton(R.string.no, new OnClickListener() {

				@Override
				public void onClick(DialogInterface dialog, int which) {
					dialog.dismiss();
				}
			});
			dialog.show();
			return true;
		}
		return super.onOptionsItemSelected(item);
	}

	@Override
	public void onLocationChange(Location location) {
		// Update Info texts
		if (counter % 2 == 0) {
			startNano = System.nanoTime();
		} else {
			endNano = System.nanoTime();
		}
		counter++;
		long nanos = 0;
		if (startNano != 0 && endNano != 0)
			nanos = (long) Math.abs(startNano - endNano);
		fps.setText(getResources().getString(R.string.fps) + ": "
				+ String.format("%.02f", (float) nanos / 1000000000));

		accuracy.setText(getResources().getString(R.string.accuracy) + ": "
				+ location.getAccuracy());

		speed.setText(getResources().getString(R.string.speed) + ": "
				+ location.getSpeed() + "m/s");

		// move camera to follow movement
		map.moveCamera(CameraUpdateFactory.newLatLng(new LatLng(location
				.getLatitude(), location.getLongitude())));

		// animate marker to current position
		animateMarker(marker, location);

	}

	@Override
	public void onRoughnessDetected(Location location, float acceleration) {
		Log.d(TAG, "Adding roughness bitmap to the main map");

		DataModel model = new DataModel();
		model.setLatitude(location.getLatitude());
		model.setLongitude(location.getLongitude());
		model.setAcceleration(acceleration);
		model.setAccuracy(location.getAccuracy());
		// add point to set to avoid duplicates
		pointsSet.add(model);
		dbHelper.addEntry(model);

		// add point to map
		map.addGroundOverlay(new GroundOverlayOptions()
				.image(BitmapDescriptorFactory
						.fromBitmap(determineBitmapColor(acceleration)))
				.position(
						new LatLng(location.getLatitude(), location
								.getLongitude()),
						determineBitmapSize(location.getAccuracy()))
				.transparency(BITMAP_TRANSPARENCY));
	}

	@Override
	public void onGpsStatusChange(GpsStatus gpsStatus) {
		int satsNumber = 0, satsUsed = 0;
		for (GpsSatellite sat : gpsStatus.getSatellites()) {
			if (sat.usedInFix()) {
				satsNumber++;
				satsUsed++;
			} else {
				satsNumber++;
			}
		}
		sats.setText(getResources().getString(R.string.sats) + ": "
				+ satsNumber);
		satsInUse.setText(getResources().getString(R.string.satsinuse) + ": "
				+ satsUsed);
	}

	private float determineBitmapSize(float accuracy) {
		float bitmapSize = BITMAP_SIZE;
		if (accuracy > GOOD_ACCURACY) {
			bitmapSize = bitmapSize + (accuracy - GOOD_ACCURACY);
		}
		return bitmapSize;
	}

	// Helps to determine which bitmap to choose for specific acceleration
	private Bitmap determineBitmapColor(float acceleration) {
		if (acceleration >= thresholdBig) {
			return bigBitmap;
		} else if (acceleration >= thresholdMedium) {
			return mediumBitmap;
		} else if (acceleration >= thresholdSmall) {
			return smallBitmap;
		} else {
			return null;
		}
	}

	@Override
	public void onAccelerationChange(float value) {
		// do nothing
	}

}